#include "server_listener.h"
#include "gui.h"
#include <QString>
#include <fstream>
#include <iostream>



void client_proxy::receive_message()
{
	server_listener::get_instance()->server->receive_message( client_id );
};



void client_proxy::status_changed( const QAbstractSocket::SocketState& state )
{
	string s;
	switch( state )
	{
	case QAbstractSocket::UnconnectedState:
		s = "The socket is not connected.";
		break;
	case QAbstractSocket::HostLookupState:
		s = "The socket is performing a host name lookup.";
		break;
	case QAbstractSocket::ConnectingState:
		s = "The socket has started establishing a connection.";
		break;
	case QAbstractSocket::ConnectedState:
		s = "A connection is established.";
		break;
	case QAbstractSocket::BoundState:
		s = "The socket is bound to an address and port (for servers).";
		break;
	case QAbstractSocket::ListeningState:
		s = "For internal use only.";
		break;
	case QAbstractSocket::ClosingState:
		s = "The socket is about to close (data may still be waiting to be written).";
		break;
	}
	server_gui::get_instance()->show_client_status( client_id, s );
};



void client_proxy::connection_error( const QAbstractSocket::SocketError& error )
{
	string s;
	switch ( error )
	{
	case QAbstractSocket::ConnectionRefusedError:
		s = "The connection was refused by the peer (or timed out).";
		break;
	case QAbstractSocket::RemoteHostClosedError:
		s = "The remote host closed the connection. Note that the client socket (i.e., this socket) will be closed after the remote close notification has been sent.";
		break;
	case QAbstractSocket::HostNotFoundError:
		s = "The host address was not found.";
		break;
	case QAbstractSocket::SocketAccessError:
		s = "The socket operation failed because the application lacked the required privileges.";
		break;
	case QAbstractSocket::SocketResourceError:
		s = "The local system ran out of resources (e.g., too many sockets).";
		break;
	case QAbstractSocket::SocketTimeoutError:
		s = "The socket operation timed out.";
		break;
	case QAbstractSocket::DatagramTooLargeError:
		s = "The datagram was larger than the operating system's limit (which can be as low as 8192 bytes).";
		break;
	case QAbstractSocket::NetworkError:
		s = "An error occurred with the network (e.g., the network cable was accidentally plugged out).";
		break;
	case QAbstractSocket::AddressInUseError:
		s = "The address specified to QUdpSocket::bind() is already in use and was set to be exclusive.";
		break;
	case QAbstractSocket::SocketAddressNotAvailableError:
		s = "The address specified to QUdpSocket::bind() does not belong to the host.";
		break;
	case QAbstractSocket::UnsupportedSocketOperationError:
		s = "The requested socket operation is not supported by the local operating system (e.g., lack of IPv6 support).";
		break;
	case QAbstractSocket::ProxyAuthenticationRequiredError:
		s = "The socket is using a proxy, and the proxy requires authentication.";
		break;
	case QAbstractSocket::SslHandshakeFailedError:
		s = "The SSL/TLS handshake failed, so the connection was closed (only used in QSslSocket).";
		break;
	case QAbstractSocket::UnfinishedSocketOperationError:
		s = "Used by QQAbstractSocketEngine only, The last operation attempted has not finished yet (still in progress in the background).";
		break;
	case QAbstractSocket::ProxyConnectionRefusedError:
		s = "Could not contact the proxy server because the connection to that server was denied.";
		break;
	case QAbstractSocket::ProxyConnectionClosedError:
		s = "The connection to the proxy server was closed unexpectedly (before the connection to the final peer was established).";
		break;
	case QAbstractSocket::ProxyConnectionTimeoutError:
		s = "The connection to the proxy server timed out or the proxy server stopped responding in the authentication phase.";
		break;
	case QAbstractSocket::ProxyNotFoundError:
		s = "The proxy address set with setProxy() (or the application proxy) was not found.";
		break;
	case QAbstractSocket::ProxyProtocolError:
		s = "The connection negotiation with the proxy server because the response from the proxy server could not be understood.";
		break;
	case QAbstractSocket::UnknownSocketError:
		s = "An unidentified error occurred.";
		break;
	}
	server_gui::get_instance()->show_client_error( client_id, s );
};



Server::Server( unsigned int plrs, QObject* parent ): QTcpServer( parent )
{
	clients_num = 0;
	max_clients = plrs;
	block_size = 0;
	ready_to_go = 0;
	clients = new QTcpSocket*[ plrs ];
	cproxy = new client_proxy*[ plrs ];
	pending_messages = new string[ plrs ];
	for ( unsigned int i = 0; i < plrs; ++i )
	{
		pending_messages[ i ] = "";
	};
	pending_messages_rcvd = 0;
	connect( this, SIGNAL( newConnection() ), this, SLOT( add_client() ) );
};



Server::~Server()
{
	for ( unsigned int i = 0; i < clients_num; ++i )
	{
		delete clients[ i ];
	}
	delete [] clients;
	delete [] cproxy;
	delete [] pending_messages;
};



void Server::add_client()
{
	if ( clients_num == max_clients )
	{
		QTcpSocket* nc = nextPendingConnection();
		server_listener::get_instance()->server->send_string( "ERROR=Too much clients! New client ignored.", nc );
		cout << "CLIENT IP: " << nc->peerAddress().toString().toStdString() << " REFUSED (TOO MUCH CLIENTS)" << endl;
		nc->disconnect();
		delete nc;		
	}
	else
	{
		clients[ clients_num ] = nextPendingConnection();
		cproxy[ clients_num ] = new client_proxy( clients_num, clients[ clients_num ] );
		connect( clients[ clients_num ], SIGNAL( stateChanged( QAbstractSocket::SocketState ) ), cproxy[ clients_num ], SLOT( status_changed( QAbstractSocket::SocketState ) ) );
		connect( clients[ clients_num ], SIGNAL( error( QAbstractSocket::SocketError ) ), cproxy[ clients_num ], SLOT( connection_error( QAbstractSocket::SocketError ) ) );
		connect( clients[ clients_num ], SIGNAL( readyRead() ), cproxy[ clients_num ], SLOT( receive_message() ) );
		connect( clients[ clients_num ], SIGNAL( disconnected() ), this, SLOT( client_disconnected() ) );
		cproxy[ clients_num ]->status_changed( clients[ clients_num ]->state() );
		ostringstream acs;
		acs << "ACCEPTED=" << clients_num;
		send_string( acs.str(), clients[ clients_num ] );
		send_string( server_listener::get_instance()->map, clients[ clients_num ] );
		if ( server_listener::get_instance()->objects != "" )
		{
			send_string( server_listener::get_instance()->objects, clients[ clients_num ] );
		}
		cout << "CLIENT " << clients_num << " IP: " << clients[ clients_num ]->peerAddress().toString().toStdString() << " CONNECTED" << endl;
		++clients_num;
	}
};



void Server::client_disconnected()
{
	close();
	QTimer::singleShot( 2000, this, SLOT( restart_listener() ) );
};



void Server::restart_listener()
{
	server_listener::get_instance()->restart_listener();
};



void Server::receive_message( unsigned int i )
{
	string message;
	for ( ; ; )
	{
		message = read_string( clients[ i ] );
		if ( message == "" )
			return;
		istringstream mess( message );
		string type;
		string msg;
		getline( mess, type, '=' );
		getline( mess, msg, '=' );

		if ( type == "SET_PLAYER" )
		{
			istringstream pls( msg );
			//PLAYER_NAME PLAYER_TYPE ( 1 -> ai, 0 -> local )
			pls.ignore( msg.size(), '"' );
			string pl_name;
			getline( pls, pl_name, '"' );
			unsigned int pl_typ_i;
			pls >> pl_typ_i;
			if ( pls.fail() )
			{
				server_gui::get_instance()->show_client_error( i, "Error while receiving player information." );
				return;
			}

			string pl_typ;
			if ( pl_typ_i == 1 )
				pl_typ = "AI";
			else
				pl_typ = "Human";
			server_gui::get_instance()->set_client_name( i, pl_name, pl_typ );
			ostringstream sout;
			sout << message << " " << i;
			pending_messages[ pending_messages_rcvd ] = sout.str();
			++pending_messages_rcvd;
			if ( pending_messages_rcvd == max_clients )
				send_pending_messages();

		}

		else if ( type == "OK" )
		{
			// 1 -> objects received, 0 -> map received, 3 -> player received
			istringstream oks( msg );
			unsigned int ok;
			oks >> ok;
			//map or objects received
			if ( ( ( ok == 0 ) && ( server_listener::get_instance()->objects == "" ) ) || ( ( ok == 1 ) && ( server_listener::get_instance()->objects != "" ) ) )
			{
				++ready_to_go;
			}
			//player received
			if ( ok == 3 )
				++ready_to_go;
			//map or objects received ( max_clients ) and all players received all players ( max_clients * max_clients )
			if ( ready_to_go == ( max_clients + 1 ) * max_clients )
			{
				resend_message( "START_BUY=0" );
			}
		}

		else if ( type == "ACTION" )
		{
			resend_message( message, i );
		}

		else if ( type == "ERROR" )
		{
			server_gui::get_instance()->show_client_error( i, msg );
		}
	}
};



string Server::read_string( QTcpSocket* r )
{
	QString noth( "" );
	QDataStream in( r );
	in.setVersion( QDataStream::Qt_4_4 );
	if ( block_size == 0 )
	{
		if ( r->bytesAvailable() < ( int )sizeof( quint16 ) )
			return noth.toStdString();
		in >> block_size;
	}
	if ( r->bytesAvailable() < block_size )
		return noth.toStdString();
	QString s;
	in >> s;
	block_size = 0;
	return s.toStdString();
};



void Server::send_string( const string& s, QTcpSocket* r )
{
	QString qs( s.c_str() );
	QByteArray block;
	QDataStream out( &block, QIODevice::WriteOnly );
	out.setVersion( QDataStream::Qt_4_4 );
	out << ( quint16 )0;
	out << qs;
	out.device()->seek( 0 );
	out << ( quint16 )( block.size() - sizeof( quint16 ) );
	r->write( block );
	while ( r->flush() );
};



void Server::send_pending_messages()
{
	for ( unsigned int c = 0; c < clients_num; ++c )
		for ( unsigned int i = 0; i < pending_messages_rcvd; ++i )
		{
			send_string( pending_messages[ i ], clients[ c ] );
		};
	for ( unsigned int i = 0; i < pending_messages_rcvd; ++i )
	{
		pending_messages[ i ] = "";
	};
	pending_messages_rcvd = 0;
};



void Server::resend_message( const string& m, int s )
{
	for ( unsigned int c = 0; c < clients_num; ++c )
	{
		if ( ( int )c == s )
			continue;
		send_string( m, clients[ c ] );
	};
};



void Server::restart()
{
	server_gui::get_instance()->show_listener_status( "Restarting server..." );
	close();
	cout << "LISTENER STOPPED" << endl;
	for ( unsigned int i = 0; i < clients_num; ++i )
	{
		send_string( "ERROR=Restarting server.", clients[ i ] );
		clients[ i ]->disconnect();
		delete clients[ i ];
		pending_messages[ i ] = "";
	}
	clients_num = 0;
	block_size = 0;
	ready_to_go = 0;
	pending_messages_rcvd = 0;
	
	if ( !listen( QHostAddress::Any, server_port ) )
	{
		cout << "LISTENER START ON PORT " << server_port << " FAILED" << endl;
		server_gui::get_instance()->show_listener_status( "Server restart failed!" );
		server_gui::get_instance()->show_network_error( "Can't start listener!" );
	}
	else
	{
		cout << "LISTENER STARTED ON PORT " << server_port << endl;
		server_gui::get_instance()->show_listener_status( "Server listening..." );
	}
};



server_listener* server_listener::get_instance()
{
	static server_listener inst(0);
	return &inst;
};



bool server_listener::load_map_file( const char *file )
{
	ifstream fin;
	fin.open( file, ifstream::in );
	ostringstream sout;
	sout << "MAP=";
	if ( !fin.good() )
	{
		cout << "ERROR WHILE OPENING MAP FILE" << endl;
		fin.close();
		return false;
	}	
	fin >> map_sx;
	fin >> map_sy;
	sout << map_sx << " " << map_sy;
	if ( !fin.good() )
	{
		cout << "ERROR WHILE READING MAP FILE" << endl;
		fin.close();
		return false;
	}
	for ( unsigned int i = 0; i < map_sx; ++i )
		for ( unsigned int j = 0; j < map_sy; ++j )
		{
			unsigned int t;
			fin >> t;
			if ( t < 10 )
			{
				cout << "ERROR WHILE READING MAP FILE" << endl;
				fin.close();
				return false;
			}
			if ( ( t > 109 ) && ( t != 20000 ) && ( t != 30000 ) )
			{
				cout << "ERROR WHILE READING MAP FILE" << endl;
				fin.close();
				return false;
			}
			if ( t > 30000 )
			{
				cout << "ERROR WHILE READING MAP FILE" << endl;
				fin.close();
				return false;
			}
			sout << " " << t;
			if ( fin.fail() )
			{
				cout << "ERROR WHILE READING MAP FILE" << endl;
				fin.close();
				return false;
			}
		}	
	fin.close();
	map = sout.str();
	cout << "MAP LOADED SUCCESSFULLY" << endl;
	return true;
};



bool server_listener::load_objects_file( const char *file )
{
	ifstream fin;
	fin.open( file, ifstream::in );
	ostringstream sout;
	sout << "OBJECTS=";
	if ( !fin.good() )
	{
		cout << "ERROR WHILE OPENING OBJECTS FILE" << endl;
		fin.close();
		return false;
	}
	string objects_line;
	for ( ; ; )
	{
		getline( fin, objects_line );
		if ( fin.fail() )
		{
			break;
		}
		if ( !parse_objects_line ( sout, objects_line ) )
		{
			cout << "ERROR WHILE READING OBJECTS FILE" << endl;
			fin.close();
			return false;
		}
	}
	fin.close();
	objects = sout.str();
	cout << "OBJECTS LOADED SUCCESSFULLY" << endl;
	return true;
};



bool server_listener::parse_objects_line( ostringstream &os, string ol )
{
	if ( ol[0] == '#' )
		return true;

	istringstream sstr( ol );

	string type;
	sstr >> type;
	if ( type == "" )
		return true;

	sstr.ignore( ol.size(), '"' );
	string name;
	getline( sstr, name, '"' );

	unsigned int px;
	unsigned int py;
	unsigned int health;
	unsigned int pl_id;

	sstr >> px;
	sstr >> py;

	if ( px >= map_sx )
		return false;
	if ( py >= map_sy )
		return false;
	
	
	if ( sstr.fail() )
		return false;


	if ( type == "UNIT" )
	{
		sstr >> health;
		sstr >> pl_id;
		if ( sstr.fail() )
			return false;
		os << type << " \"" << name << "\" " << px << " " << py << " " << health << " " << pl_id << endl;
		cout << "LOADED UNIT: " << name << "\t[" << px << "," << py << "]" << endl;
	}
	else if ( type == "BONUS" )
	{
		os << type << " \"" << name << "\" " << px << " " << py << endl;
		cout << "LOADED BONUS: " << name << "\t[" << px << "," << py << "]" << endl;
	}
	else
	{
		return false;
	}
	return true;
};



void server_listener::create_listener( unsigned int plrs )
{
	if ( server == 0 )
	{
		server = new Server( plrs );
		server->setProxy( QNetworkProxy::NoProxy );
	}
};



bool server_listener::start_listener( unsigned int port )
{
	server->set_port( ( quint16 )port );
	if ( !server->listen( QHostAddress::Any, ( quint16 )port ) )
	{
		cout << "LISTENER START ON PORT " << port << " FAILED" << endl;
		server_gui::get_instance()->show_network_error( "Can't start listener!" );
		return false;
	}
	else
	{
		cout << "LISTENER STARTED ON PORT " << server->serverPort() << endl;
		server_gui::get_instance()->show_listener_status ( "Server listening..." );	
		return true;
	}
};



void server_listener::restart_listener()
{
	server->restart();
	server_gui::get_instance()->show_listener_restart();
};